OpenAIRE Graph API V3 — Follow-up Report
Date: 2026-07-15
Reporter: S. Mok, s.mok@utwente.nl
APIs tested: OpenAIRE Graph API V3 (/graph/v3/*), Scholexplorer V3 (/v3/Links)
Method: All issues from the previous report (2026-06-04) were re-verified against the live V3 endpoints using OAuth2 client credentials authentication, cross-referenced against the published OpenAPI specifications and the updated documentation at graph.openaire.eu/docs.
Thank you for releasing V3! This is a follow-up to the bug report we sent previously. We have re-tested every issue against the new V3 endpoints to see what has been resolved and what remains. We also list a few new issues we discovered while testing V3. As before, we report each issue separately without making assumptions about shared root causes.
Result: of the 21 issues in the previous report, 9 are resolved and 12 are still present in V3. We also found 4 new issues.
| ID | Severity | Category | Endpoint | Issue | V3 Status |
|---|---|---|---|---|---|
| BUG-S1 | Critical | Server Error | /v3/persons |
givenName filter → 500 |
✅ Fixed |
| BUG-S2 | Critical | Server Error | /v3/persons |
lastName filter → 500 |
✅ Fixed |
| BUG-D1 | High | Wrong Docs | All entities | debugQuery documented but doesn’t exist |
✅ Resolved (removed) |
| BUG-D2 | Medium | Missing Docs | Research products | rorId works but not documented |
✅ Fixed |
| BUG-D3 | Medium | Missing Docs | All entities | logicalOperator works but not documented |
✅ Fixed |
| BUG-D4 | High | Missing Docs | Persons | No filtering docs (404) | ✅ Fixed |
| BUG-D5 | High | Missing Docs | Links | Entire endpoint undocumented | ❌ Still present |
| BUG-D6 | Low | Wrong Docs | Data sources | legalShortName description says “organization” |
❌ Still present |
| BUG-D7 | Low | Wrong Docs | Scholix spec | Title typo “ScholeExplorer” | ❌ Still present |
| BUG-D8 | Low | Wrong Docs | Scholix spec | page marked required but optional |
❌ Still present |
| BUG-D9 | Low | Wrong Docs | Data sources spec | sortBy description says “organizations” | ❌ Still present |
| BUG-D10 | High | Wrong Docs | Persons spec | sortBy includes non-functional startDate/endDate |
✅ Fixed |
| BUG-D11 | Medium | Missing Docs | V1/V2 | V2 not documented; no deprecation notice | ✅ Fixed |
| BUG-O1 | Critical | Behavior | /v3/research-products/links |
pageSize=100 silently returns 10 |
❌ Still present |
| BUG-O2 | Critical | Behavior | Scholix | size=100 silently returns 10 |
❌ Still present |
| BUG-O3 | Medium | Behavior | Research products | Error message omits popularity sort field |
❌ Still present |
| BUG-O4 | Low | Behavior | Data sources | Error message says “organizations” | ❌ Still present |
| BUG-O5 | Medium | Data Quality | Scholix | Publisher IDURL always null |
✅ Fixed |
| BUG-O6 | Medium | Consistency | Links | Different response header format | ❌ Still present |
| BUG-O7 | High | Consistency | Graph API | Inconsistent page indexing (0 vs 1) | ❌ Still present |
| BUG-O8 | High | Behavior | Links | Default pageSize=10, spec says 100 | ❌ Still present |
The following issues from the previous report are now resolved. Thank you!
givenName and lastName filters on /v3/persons now work correctly, returning 200 with filtered results (previously HTTP 500).debugQuery parameter has been removed from the public API.rorId is now documented on the research products endpoint.logicalOperator is now documented on all entities (and now also supports NOT in addition to AND/OR).sortBy parameter now correctly documents only relevance (the non-functional startDate/endDate were removed).IDURL values are now populated (no longer consistently null).We also noticed and appreciate several general improvements in V3: the kebab-case URLs, many new and useful filter parameters across all entities, full cursor-based pagination on the regular endpoints, and proper pageSize validation (the regular endpoints now correctly accept up to 100 and reject larger values with a clean 400 instead of silently capping).
The following 12 issues from the previous report are still present in V3. All reproductions below use the new V3 endpoints and were verified on 2026-07-15.
Endpoint: GET /v3/research-products/links and GET /v3/research-products/links/relations-info
Both endpoints exist in the V3 OpenAPI spec and work on the live API, but are still not documented anywhere on graph.openaire.eu/docs. The page /docs/apis/graph-api/research-products/links returns HTTP 404. There is no mention of the links endpoint or the relations-info endpoint in the navigation, searching, or filtering documentation.
legalShortName description is wrongLocation: Documentation website (data sources filtering table) and V3 OpenAPI spec, /v3/datasources
The description for legalShortName still reads: “The legal name of the organization in short form.” This is a data source parameter — it should say “The legal name of the data source in short form.”
This is a copy-paste error from the organizations section, and it now appears in both the documentation website and the OpenAPI spec.
Location: Scholexplorer OpenAPI spec (/api-docs/Scholexplorer%20API%20V3.0)
The spec title is still "ScholeExplorer APIs" — should be "Scholexplorer APIs" (the correct spelling used elsewhere). The extra e in “Scholeexplorer” is still present.
page parameter still marked as requiredLocation: Scholexplorer OpenAPI spec, /v3/Links endpoint
The page parameter still has "required": true in the spec, but the API works fine without it (returns page 0 by default).
GET https://api.scholexplorer.openaire.eu/v3/Links?sourcePid=10.1016/j.respol.2021.104226&size=2
→ 200 {"currentPage": 0, "totalLinks": 121, ...}
Reproduction: curl "https://api.scholexplorer.openaire.eu/v3/Links?sourcePid=10.1016/j.respol.2021.104226&size=2"
sortBy OpenAPI spec description says “organizations”Location: V3 OpenAPI spec, /v3/datasources sortBy parameter
The sortBy parameter description still reads: “The field should be in the format ‘fieldname ASC|DESC’, organizations can be only sorted by the ‘relevance’.” This is a copy-paste error from the organizations endpoint — it should say “data sources” instead of “organizations”.
pageSize=100 silently returns 10 results on links endpointEndpoint: GET /v3/research-products/links
Affected parameter: pageSize
Still present in V3. Requesting pageSize=100 silently falls back to 10 results with no error. The totalPages value confirms the server is using pageSize=10 internally.
| pageSize | Results returned | totalPages | Expected |
|---|---|---|---|
| 10 | 10 | 13 | 13 |
| 50 | 50 | 3 | 3 |
| 99 | 99 | 2 | 2 |
| 100 | 10 | 13 | 2 |
Note: the regular V3 endpoints (e.g. /v3/research-products) now handle this correctly — pageSize=100 returns 100 and larger values return a proper 400. The defect is isolated to the links endpoint.
GET https://api.openaire.eu/graph/v3/research-products/links?sourcePid=10.1016/j.respol.2021.104226&pageSize=100&page=0
→ 200 {"header":{"page":0,"totalPages":13,"totalLinks":121},"results":[...10 items...]}
Reproduction: curl "https://api.openaire.eu/graph/v3/research-products/links?sourcePid=10.1016/j.respol.2021.104226&pageSize=100&page=0"
Effective maximum: 99
size=100 silently returns 10 results on ScholixEndpoint: GET /v3/Links (Scholexplorer)
Affected parameter: size
Still present. Identical behavior to BUG-O1. Requesting size=100 (the documented maximum: “max is 100”) silently returns 10 results.
| size | Results returned | totalPages | Expected |
|---|---|---|---|
| 50 | 50 | 3 | 3 |
| 99 | 99 | 2 | 2 |
| 100 | 10 | 13 | 2 |
| 200 | 10 | 13 | 2 |
GET https://api.scholexplorer.openaire.eu/v3/Links?sourcePid=10.1016/j.respol.2021.104226&size=100
→ 200 {"currentPage":0,"totalLinks":121,"totalPages":13,"result":[...10 items...]}
Reproduction: curl "https://api.scholexplorer.openaire.eu/v3/Links?sourcePid=10.1016/j.respol.2021.104226&size=100"
Effective maximum: 99
popularityEndpoint: GET /v3/research-products
Still present. When providing an invalid sortBy value, the error message lists valid fields as:
‘relevance’, ‘publicationDate’, ‘dateOfCollection’, ‘influence’, ‘citationCount’, ‘impulse’
But popularity is also a valid sort field (confirmed working with sortBy=popularity DESC, which returns 200). The error message is missing popularity, which makes it appear invalid when users encounter it via the error message.
GET https://api.openaire.eu/graph/v3/research-products?sortBy=invalid+DESC&pageSize=1
→ 400 {"message":"...research products can be only sorted by the 'relevance', 'publicationDate', 'dateOfCollection', 'influence', 'citationCount', 'impulse'."}
Note: The OpenAPI spec correctly includes popularity in the regex pattern. Only the error message is wrong.
Reproduction: curl "https://api.openaire.eu/graph/v3/research-products?sortBy=invalid+DESC&pageSize=1"
Endpoint: GET /v3/datasources
Still present. When providing an invalid sortBy value, the error message reads:
“organizations can be only sorted by the ‘relevance’”
This is the data sources endpoint, not organizations. Copy-paste error in the error message (same root cause as BUG-D9).
GET https://api.openaire.eu/graph/v3/datasources?sortBy=invalid+DESC&pageSize=1
→ 400 {"message":"...organizations can be only sorted by the 'relevance'."}
Reproduction: curl "https://api.openaire.eu/graph/v3/datasources?sortBy=invalid+DESC&pageSize=1"
Endpoints: GET /v3/research-products/links vs all other V3 Graph API endpoints
Still present. The links endpoint returns a completely different header structure from the other V3 Graph API endpoints:
| Field | Regular endpoints | Links endpoint | Scholix |
|---|---|---|---|
numFound |
✅ | ❌ | ❌ |
maxScore |
✅ | ❌ | ❌ |
page |
✅ (1-indexed) | ✅ (0-indexed) | ✅ (as currentPage, 0-indexed) |
pageSize |
✅ | ❌ | ❌ |
totalPages |
❌ | ✅ | ✅ |
totalLinks |
❌ | ✅ | ✅ |
nextCursor |
✅ (with cursor param) | ❌ | ❌ |
The links endpoint also still lacks cursor-based pagination support, despite being a V3 Graph API endpoint.
Affected endpoints: All V3 Graph API endpoints
/v3/research-products, /v3/projects, etc.): 1-indexed (page=1 is the first page, page=0 returns 400)/v3/research-products/links): 0-indexed (page=0 is the first page)/v3/Links): 0-indexed (page=0 is the first page)This is particularly confusing because the links endpoint is a V3 Graph API endpoint but uses different page indexing than the other V3 endpoints.
GET https://api.openaire.eu/graph/v3/research-products?search=covid&page=0&pageSize=1
→ 400 {"message":"page must be greater than 0"}
GET https://api.openaire.eu/graph/v3/research-products/links?sourcePid=10.1016/j.respol.2021.104226&page=0
→ 200 (first page of results)
pageSize is 10, not 100 as spec claimsEndpoint: GET /v3/research-products/links
Spec says: "default": 100
Actual: Default is 10
GET https://api.openaire.eu/graph/v3/research-products/links?sourcePid=10.1016/j.respol.2021.104226&page=0
→ 200 {"header":{"page":0,"totalPages":13,"totalLinks":121},"results":[...10 items...]}
With 121 total links and totalPages=13, the effective page size is 10. The spec claims the default is 100, which would produce totalPages=2.
The following issues were discovered while testing V3. They were not in the previous report.
info.version field still missing from all OpenAPI specificationsLocation: All OpenAPI specifications (V3, V4-BETA, Scholexplorer)
None of the OpenAPI specifications include an info.version field. This was noted in the previous report’s general remarks and remains unresolved. Including info.version in each spec would let consumers programmatically detect which API version they are interacting with.
/v3/api-docs endpoint returns HTTP 500Location: https://api.scholexplorer.openaire.eu/v3/api-docs
The standard OpenAPI root path returns HTTP 500. The specification is only reachable via the group-specific path /api-docs/Scholexplorer%20API%20V3.0. Most OpenAPI/Swagger tooling expects the root api-docs path to work, so this causes integration failures.
GET https://api.scholexplorer.openaire.eu/v3/api-docs
→ 500
Affected endpoints: All V3 Graph API filter parameters that support logical operators
Filter values containing spaces, parentheses, or logical operators must be wrapped in double quotes. This is a powerful feature (it enables inline expressions like countryCode=("US" OR "GB") AND NOT "DE"), but the quoting requirement itself is not documented, and it is easy to hit by accident:
GET /v3/research-products?accessRightLabel=Open Access&pageSize=1
→ 400 {"message":"Invalid value for 'accessRightLabel'. Values containing spaces, parentheses or logical operators must be wrapped in double quotes, e.g. accessRightLabel=(\"value\")."}
GET /v3/research-products?accessRightLabel="Open Access"&pageSize=1
→ 200 (returns results)
Recommendation: Document the quoting and logical-operator syntax for filter values prominently in the filtering guide, including the list of operators (AND, OR, NOT) and examples.
Affected parameters: accessRightLabel, sdgLabel, openAccessColor, influenceClass, popularityClass, impulseClass, citationCountClass
These parameters accept only a specific set of string values, but the OpenAPI spec declares them as plain type: string without an enum. The only way to discover the valid values is to submit an invalid one and read the 400 error message. For example:
GET /v3/research-products?accessRightLabel=OPEN
→ 400 {"message":"Invalid value: 'OPEN' in 'accessRightLabel'. Allowed values are: ['Open Access', 'Closed Access', 'Restricted', 'Open Source', 'Embargo', 'Unknown']"}
Known allowed values discovered this way:
accessRightLabel: Open Access, Closed Access, Restricted, Open Source, Embargo, UnknownopenAccessColor: bronze, gold, hybridinfluenceClass / popularityClass / impulseClass / citationCountClass: C1, C2, C3, C4, C5sdgLabel: the full set of Sustainable Development Goal labels (e.g. 1. No poverty … 16. Peace & justice)Recommendation: Declare these as enum constraints in the OpenAPI spec so that clients and code generators can discover the valid values without trial and error.
As a final general remark (carried over from the previous report): the version numbering remains somewhat confusing. The OpenAPI spec URLs live under /graph/v3/api-docs/, where v3 is the Springdoc group name rather than a clear API version marker; the actual V3 API paths use /v3/; and none of the specs include an info.version field. We understand V3 is now the single recommended version (V1/V2 deprecated), which already simplifies things considerably. Adding info.version to each spec would make the remaining ambiguity easy to resolve programmatically.